package com.mazaiting.instructions.references;

import com.mazaiting.instructions.base.Index16Instruction;
import com.mazaiting.rtda.MtFrame;
import com.mazaiting.rtda.MtOperandStack;
import com.mazaiting.rtda.MtSlots;
import com.mazaiting.rtda.heap.*;

import java.util.Objects;

/**
 * 字段指令
 *
 * @author mazaiting
 * @since 2021/8/17
 */
public class FieldInstruction {

    /**
     * putfield指令给实例变量赋值，它需要三个操作数。前两个操作数是常量池索引和变量值，
     * 用法和putstatic一样。第三个操作数是对象引用，从操作数栈中弹出
     */
    public static class PutField extends Index16Instruction {
        /**
         * 第一，解析后的字段必须是实例字段，否则抛出IncompatibleClassChangeError。
         * 第二，如果是final字段，则只能在构造函数中初始化，否则抛出IllegalAccessError
         * @param frame 栈帧
         */
        @Override
        public void execute(MtFrame frame) {
            // 获取栈帧方法
            MtMethod currentMethod = frame.getMethod();
            // 获取当前类
            MtClass currentMtClass = currentMethod.getMtClass();
            // 获取常量池
            MtConstantPool pool = currentMtClass.getConstantPool();
            // 当前字段引用
            MtConstant.MtFieldRef fieldRef = (MtConstant.MtFieldRef) pool.getConstant(index);
            // 解析字段
            MtField field = fieldRef.resolvedField();
            // 判断是否为静态字段
            if (field.isStatic()) {
                throw new RuntimeException("java.lang.IncompatibleClassChangeError");
            }
            // 判断是否为最终字段
            if (field.isFinal()) {
                if (!Objects.equals(currentMtClass, field.getMtClass()) || !Objects.equals(currentMethod.getName(), "<init>")) {
                    throw new RuntimeException("java.lang.IllegalAccessError");
                }
            }

            // 获取字段描述, 槽
            String descriptor = field.getDescriptor();
            int slotId = field.getSlotId();
            // 获取操作数栈
            MtOperandStack stack = frame.getOperandStack();
            // 对象引用
            MtObject ref;
            switch (descriptor.substring(0, 1)) {
                case "Z":
                case "B":
                case "C":
                case "S":
                case "I":
                    int iVal = stack.popInt();
                    ref = stack.popRef();
                    if (Objects.isNull(ref)) {
                        throw new RuntimeException("java.lang.NullPointerException");
                    }
                    ref.getFields().setInt(slotId, iVal);
                    break;
                case "F":
                    float fVal = stack.popFloat();
                    ref = stack.popRef();
                    if (Objects.isNull(ref)) {
                        throw new RuntimeException("java.lang.NullPointerException");
                    }
                    ref.getFields().setFloat(slotId, fVal);
                    break;
                case "J":
                    long lVal = stack.popLong();
                    ref = stack.popRef();
                    if (Objects.isNull(ref)) {
                        throw new RuntimeException("java.lang.NullPointerException");
                    }
                    ref.getFields().setLong(slotId, lVal);
                    break;
                case "D":
                    double dVal = stack.popDouble();
                    ref = stack.popRef();
                    if (Objects.isNull(ref)) {
                        throw new RuntimeException("java.lang.NullPointerException");
                    }
                    ref.getFields().setDouble(slotId, dVal);
                    break;
                case "L":
                case "[":
                    MtObject oVal = stack.popRef();
                    ref = stack.popRef();
                    if (Objects.isNull(ref)) {
                        throw new RuntimeException("java.lang.NullPointerException");
                    }
                    ref.getFields().setRef(slotId, oVal);
                    break;
            }
        }

        @Override
        public String toString() {
            return "putfield " + index;
        }
    }

    /**
     * getfield指令获取对象的实例变量值，然后推入操作数栈，它需要两个操作数。第一个操作数是uint16索引，
     * 用法和前面三个指令一样。第二个操作数是对象引用，用法和putfield一样
     * 弹出对象引用，如果是null，则抛出NullPointerException
     * 根据字段类型，获取相应的实例变量值，然后推入操作数栈。
     */
    public static class GetField extends Index16Instruction {
        @Override
        public void execute(MtFrame frame) {
            // 当前栈帧常量池
            MtConstantPool pool = frame.getMethod().getMtClass().getConstantPool();
            // 获取字段引用
            MtConstant.MtFieldRef fieldRef = (MtConstant.MtFieldRef) pool.getConstant(index);
            // 获取字段
            MtField field = fieldRef.resolvedField();
            // 判断是否为静态字段
            if (field.isStatic()) {
                throw new RuntimeException("java.lang.IncompatibleClassChangeError");
            }
            // 操作数栈
            MtOperandStack stack = frame.getOperandStack();
            // 引用
            MtObject ref = stack.popRef();
            // 判断对象是否为空
            if (Objects.isNull(ref)) {
                throw new RuntimeException("java.lang.NullPointerException");
            }
            // 获取字段描述, 槽
            String descriptor = field.getDescriptor();
            int slotId = field.getSlotId();
            // 获取字段槽
            MtSlots slots = ref.getFields();
            switch (descriptor.substring(0, 1)) {
                case "Z":
                case "B":
                case "C":
                case "S":
                case "I":
                    stack.pushInt(slots.getInt(slotId));
                    break;
                case "F":
                    stack.pushFloat(slots.getFloat(slotId));
                    break;
                case "J":
                    stack.pushLong(slots.getLong(slotId));
                    break;
                case "D":
                    stack.pushDouble(slots.getDouble(slotId));
                    break;
                case "L":
                case "[":
                    stack.pushRef(slots.getRef(slotId));
                    break;
            }
        }

        @Override
        public String toString() {
            return "getfield " + index;
        }
    }

}


















